﻿using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Cubicle.Core.Processors;
using Cubicle.Core.Shared;
using Cubicle.Excel.AddIn;
using Cubicle.Excel.Binding;
using Cubicle.Excel.Config;
using ExcelDna.Integration;
using NLog;

namespace Cubicle.Excel.Shared
{
    public static class CustomHelpers
    {
        // Nlog
        private static Logger _logger = LogManager.GetCurrentClassLogger();
        public static readonly Guid guidIRtdServer = new Guid("EC0E6191-DB51-11D3-8F3E-00C04F3651B8");

        private static object Invoke(this string typeName, string methodName, params object[] parameters)
        {
            try
            {
                var type = Type.GetType(typeName);
                var method = type.GetMethod(methodName, parameters.Select(x => x.GetType()).ToArray());
                return method.Invoke(null, parameters);
            }
            catch
            {
                return null;
            }
        }

        public static IObservable<T[]> InitBuffer<T>(this IObservable<T> obs, int bufferSize, T init)
        {
            bufferSize = Math.Max(1, bufferSize);
            return Enumerable.Repeat(init, bufferSize)
                             .ToObservable()
                             .Concat(obs)
                             .Buffer(bufferSize, 1)
                             .Select(x => x.ToArray<T>());
        }

        private static bool IsMarkedForDnaExport(this MethodInfo mi)
        {
            object[] atts = mi.GetCustomAttributes(false);
            foreach (object att in atts)
            {
                Type attType = att.GetType();
                if (TypeHasAncestorWithFullName(attType, "ExcelDna.Integration.ExcelFunctionAttribute") ||
                    TypeHasAncestorWithFullName(attType, "ExcelDna.Integration.ExcelCommandAttribute"))
                {
                    return true;
                }
            }
            return false;
        }

        private static bool IsMarkedForCubicleExport(this MethodInfo mi)
        {
            object[] atts = mi.GetCustomAttributes(false);
            foreach (object att in atts)
            {
                Type attType = att.GetType();
                return (TypeHasAncestorWithFullName(attType, typeof (BindingAttribute).FullName));
            }
            return false;
        }

        private static bool TypeHasAncestorWithFullName(Type type, string fullName)
        {
            if (type == null) return false;
            if (type.FullName == fullName) return true;
            return TypeHasAncestorWithFullName(type.BaseType, fullName);
        }

        public static void AutoRegisterDnaMethods(Assembly asm, bool explicitExports)
        {
            if (asm.IsDynamic) return;

            // Register RTD server types
            var rtdServerTypes = asm
                .GetTypes()
                .SelectMany(t => t.GetInterfaces())
                .Where(i => i.GUID == guidIRtdServer)
                .ToList();

            if (rtdServerTypes.Any())
                "ExcelDna.Integration.Rtd.RtdRegistration".Invoke("RegisterRtdServerTypes", rtdServerTypes);

            // Install synchronization manager
            // always installed

            // Register methods
            var asmTypes = asm.GetTypes();
            var excelMethods = asmTypes
                .Where(t => t.IsClass && t.IsPublic && !t.IsArray && !(t.IsGenericType && t.ContainsGenericParameters) && t.Namespace != "My")
                .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static))
                .Where(m => !explicitExports || m.IsMarkedForDnaExport())
                .ToList();

            ExcelIntegration.RegisterMethods(excelMethods);
            ExcelAsyncUtil.QueueAsMacro(() => ExcelIntegration.RegisterMethods(excelMethods));

            // Register COM RTD Types (not supported yet)

            // Create add-in classes and execute AutoOpen()
            var addins = asmTypes
                .Where(t => t.GetInterface(typeof (IExcelAddIn).Name) != null)
                .Select(Activator.CreateInstance)
                .Cast<IExcelAddIn>()
                .Select(addin => new {AddIn = addin, AutoOpen = addin.GetType().GetMethod("AutoOpen") })
                .Where(x => x.AutoOpen != null)
                .ToList();

            foreach (var x in addins)
            {
                _logger.Info("Executing 'AutoOpen' on {0}", x.AddIn.GetType().FullName);
                x.AutoOpen.Invoke(x.AddIn, null);
            }
        }

        public static void AutoRegisterCubicleMethods(Assembly asm)
        {
            if (asm.IsDynamic) return;

            var excelMethods = asm
                .GetTypes()
                .Where(t => t.IsClass && t.IsPublic && !t.IsArray && !(t.IsGenericType && t.ContainsGenericParameters) && t.Namespace != "My")
                .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.Static))
                .Where(m => m.IsMarkedForCubicleExport())
                .ToList();

            foreach (var mi in excelMethods)
            {
                var attrib = mi.GetCustomAttributes(typeof (BindingAttribute), true).Cast<BindingAttribute>().First();
                var bd = new FunctionDescription()
                    {
                        Async = attrib.IsAsync,
                        BindToHandle = attrib.BindToHandle,
                        ResolveParameters = attrib.ResolveParameters,
                        MethodName = String.IsNullOrEmpty(attrib.Name) ? mi.Name : attrib.Name,
                        IsThreadSafe = false,
                        IsExceptionSafe = false,
                        IsStatic = true,
                        IsRemote = false,
                        IsVolatile = false,
                        Target = mi.DeclaringType.FullName
                    };

                CubicleAddIn.Binder.Process(bd);
            }
        }

        public static Instruction CreateInstruction(
            string handle,
            string memberName,
            object[,] parameters,
            string key,
            object[] methodArgs = null,
            object[] classArgs = null)
        {
            string[] methodArgsStr = null;
            string[] classArgsStr = null;

            if (methodArgs != null && methodArgs[0].GetType() != typeof(ExcelMissing))
            {
                methodArgsStr = new string[methodArgs.Length];
                for (int i = 0; i < methodArgs.Length; i++)
                    methodArgsStr[i] = (string)methodArgs[i];
            }
            if (classArgs != null && classArgs[0].GetType() != typeof(ExcelMissing))
            {
                classArgsStr = new string[classArgs.Length];
                for (int i = 0; i < classArgs.Length; i++)
                    classArgsStr[i] = (string)classArgs[i];
            }

            var pars = ExcelHelpers.DoubleToJaggedOrArray(parameters);
            return new Instruction(handle, memberName, key, pars, methodArgsStr, classArgsStr);
        }
    }
}
